package com.wei.czz.common.aspect;

import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.tool.Result;
import com.wei.czz.common.utils.ContextUtils;
import com.wei.czz.common.utils.HttpUtils;
import com.wei.czz.framework.common.entity.IpAddressEntity;
import com.wei.czz.framework.common.entity.LogEntity;
import com.wei.czz.framework.common.service.AsyncService;
import com.wei.czz.framework.common.service.IpAddressService;
import com.wei.czz.framework.common.helper.LimitHelper;
import com.wei.czz.framework.common.service.LogService;
import com.wei.czz.framework.security.manager.RequestLimitManager;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Objects;
import java.util.Optional;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2021-05-24 14:07:00
 * className: RequestAspect http请求接口切面类
 * version: 1.0
 * description:
 *  此类的作用：
 *      记录http请求日志
 */
@Order(1)
@Aspect
@Component
@AllArgsConstructor
public class RequestAspect {

    private static final Logger log = LoggerFactory.getLogger(RequestAspect.class);

    private final LogService logService;

    private final RequestLimitManager requestLimitManager;

    private final LimitHelper limitHelper;

    private final IpAddressService ipAddressService;

    private final AsyncService asyncService;

    /**
     * com.wei.blog.framework包路径下，index模块、common模块和system模块的controller包下，所有的接口方法作为切点
     */
    @Pointcut("execution(* com.wei.czz.framework.*.controller.*.*(..))")
    public void logPoint() {}

    /**
     * Aop环绕通知方法
     */
    @Around("logPoint()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request = HttpUtils.getHttpServletRequest();
        // 获取请求源的ip地址
        Long userId = ContextUtils.get(Constant.USER_ID);
        String requestIp = ContextUtils.get(Constant.REQUEST_IP);
        String visitId = request.getHeader(Constant.CZZ_VISIT_ID);

        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 获取目标类名称
        String className = methodSignature.getDeclaringType().getSimpleName();
        // 获取目标方法对象
//        Method method = methodSignature.getMethod();

        LogEntity logEntity = new LogEntity();
        // 设置用户主键、请求源ip地址、请求接口地址、请求接口类路径、请求接口方法名、请求类型和接口开始处理时间属性值
        logEntity.setUserId(Optional.ofNullable(userId).orElse(CommonEnum.ZERO.getLongValue()))
                .setVisitId(Optional.ofNullable(visitId).orElse(StringUtils.EMPTY))
                .setIp(requestIp)
                .setUri(request.getRequestURI())
                .setType(request.getMethod())
                .setClassName(className)
                .setMethod(methodSignature.getName())
                .setRequestId(MDC.get(Constant.TRACE_ID))
                .setStartTime(System.currentTimeMillis());

        try {

            // 执行目标方法
            Object result = pjp.proceed();

            if (result instanceof Result) {
                Result<?> r = (Result<?>) result;
                logEntity.setCode(r.getCode())
                        .setMessage(r.getMsg());
            } else {
                logEntity.setCode(ResultEnum.SUCCESS.getCode())
                        .setMessage(ResultEnum.SUCCESS.getMsg());
            }

            return result;
        } catch (Throwable throwable) {

            if (throwable instanceof CzzException) {
                CzzException be = (CzzException) throwable;

                logEntity.setCode(be.getCode());
                logEntity.setMessage(be.getMessage());
            } else if (throwable instanceof ConstraintViolationException) {
                // 此处对JSR303校验请求url携带的参数错误提示进行整合
                StringBuilder sb = new StringBuilder();
                for (ConstraintViolation<?> constraintViolation : ((ConstraintViolationException) throwable).getConstraintViolations()) {
                    sb.append(constraintViolation.getMessage()).append(',');
                }
                // 删除最后一个字符‘,’
                sb.delete(sb.length() - 1, sb.length());

                logEntity.setCode(ResultEnum.REQUEST_FAIL.getCode());
                logEntity.setMessage(sb.toString());
            } else {
                String message = throwable.getMessage();
                if (StringUtils.isBlank(message)) {
                    message = ResultEnum.SERVICE_FAIL.getMsg();
                } else {
                    message = message.substring(0, Math.min(message.length(), 1000));
                }
                logEntity.setCode(ResultEnum.SERVICE_FAIL.getCode());
                logEntity.setMessage(message);
            }
            // 此处继续往外抛出异常，让全局异常配置可以监听到
            throw throwable;

        } finally {
            // 接口处理结束时间
            logEntity.setEndTime(System.currentTimeMillis());

            log.info("{} {} {} {} 耗时：{}毫秒", logEntity.getType(), logEntity.getUri(), logEntity.getClassName(),
                    logEntity.getCode(), logEntity.getEndTime() - logEntity.getStartTime());

//            final String _resubmitKey = resubmitKey;
            // 异步保存操作日志
            asyncService.execute(() -> {
                try {
                    // 获取ip关联的现实地址
                    IpAddressEntity ipAddress = ipAddressService.getIpAddress(logEntity.getIp());
                    logEntity.setAddress(ipAddress.getRequestAddress());

                    // 保存日志
                    logService.saveOrEdit(logEntity);
                } catch (Exception e) {
                    log.error("系统操作日志保存失败。{} class={} message={}", logEntity, e.getClass(), e.getMessage());
                }

                try {
                    if (!ResultEnum.SUCCESS.getCode().equals(logEntity.getCode())) {

                        if (ResultEnum.SERVICE_FAIL.getCode().equals(logEntity.getCode())) {
                            // 异常限流计算操作
                            limitHelper.failMath(logEntity.getUri());
                        } else {
                            if (Objects.isNull(userId)) {
                                requestLimitManager.requestFail(requestIp, logEntity.getCode());
                            } else {
                                // 用户请求失败风控操作
                                requestLimitManager.userRequestFail(userId, logEntity.getCode());
                            }
                        }
                    }
                } catch (Exception e) {
                    log.error("风控操作失败。class={} message={}", e.getClass(), e.getMessage());
                }
            });
        }
    }

}
